import math, pygame

from strange_io import SIGNATURE, _load_bytearray, get_format
from strange_io.dictionary import StrangeDictionary
from strange_io.text import StrangeText
from strange_io.float import StrangeFloat
from strange_io.array import StrangeArray
from strange_io.integer import StrangeInteger

pygame.init()

class StrangeVectors:
    def __init__(self, width=256, height=256):
        self.width:int = width
        self.height:int = height
        self.surfaces = {}
        self.elements = {}

    def dump(self) -> dict:
        out = {}
        out['height'] = self.height
        out['width'] = self.width
        out['surfaces'] = self.surfaces
        out['elements'] = self.elements
        return out

    def load(self, image) -> dict:
        self.height = image['height']
        self.width = image['width']
        self.surfaces = image['surfaces']
        self.elements = image['elements']

    def dump_bytes(self) -> bytes:
        return self.encode(self.dump())

    def load_bytes(self, image:bytes):
        self.load(self.decode(image))

    def dump_file(self, path):
        """ Dump document to file """
        out = bytearray()
        header = bytearray(SIGNATURE + b'vectors\n')
        out += header
        out += self.dump_bytes()
        with open(path, 'wb') as f:
            f.write(out)

    def load_file(self, path, ignore_type=False) -> dict:
        """ Load document from file """
        if get_format(path) == b'vectors' or ignore_type:
            file = _load_bytearray(path, remove_type=True)
            self.load_bytes(file)
        else:
            raise TypeError('Attempted to load non-vectors document as vectors document')

    @staticmethod
    def encode(img:dict) -> bytes:
        out = bytearray()
        text = StrangeText()
        array = StrangeArray()
        #region width
        number = img['width']
        if number == 0:
            length = 1
        else:
            length = math.ceil(number / 255)
        if length > 255:
            raise ValueError()
        out += length.to_bytes(1, 'big')
        out += img['width'].to_bytes(length, 'big')
        #endregion
        #region height
        number = img['height']
        if number == 0:
            length = 1
        else:
            length = math.ceil(number / 255)
        if length > 255:
            raise ValueError()
        out += length.to_bytes(1, 'big')
        out += img['height'].to_bytes(length, 'big')
        #endregion
        #region surfaces
        surface_bytes = {}
        for surface_name in img['surfaces']:
            surface_bytes[surface_name] = bytearray()
            surface_cfg = img['surfaces'][surface_name]
            #region position, size and rotation
            array.append(StrangeFloat.encode(float(surface_cfg['position'][0]), 'big'))
            array.append(StrangeFloat.encode(float(surface_cfg['position'][1]), 'big'))
            array.append(StrangeFloat.encode(float(surface_cfg['size'][0]), 'big'))
            array.append(StrangeFloat.encode(float(surface_cfg['size'][1]), 'big'))
            array.append(StrangeFloat.encode(float(surface_cfg['rotation']), 'big'))
            surface_bytes[surface_name] += array.dump_bytes()
            array.load([])
            #endregion
        #region encoded_surfaces
        encoded_surfaces = StrangeDictionary.encode(surface_bytes)
        number = len(encoded_surfaces)
        if number == 0:
            length = 1
        else:
            length = math.ceil(number / 255)
        if length > 255:
            raise ValueError()
        out += bytes([length])
        out += number.to_bytes(length, 'big')
        out += encoded_surfaces
        del number, length
        #endregion
        #endregion
        #region elements
        element_bytes = {}
        for element_name in img['elements']:
            element_bytes[element_name] = bytearray()
            element_cfg = img['elements'][element_name]
            text.load(element_cfg['surface'])
            array.append(text.dump_bytes())
            text.load(element_cfg['type'])
            array.append(text.dump_bytes())
            if element_cfg['type'] == 'rectangle':
                array.append(StrangeFloat.encode(float(element_cfg['position'][0]), 'big'))
                array.append(StrangeFloat.encode(float(element_cfg['position'][1]), 'big'))
                array.append(StrangeFloat.encode(float(element_cfg['size'][0]), 'big'))
                array.append(StrangeFloat.encode(float(element_cfg['size'][1]), 'big'))
                array.append(StrangeInteger.encode(int(element_cfg['width']), 'big'))
                array.append(bytes([element_cfg['color'][0], element_cfg['color'][1], element_cfg['color'][2]]))
            elif element_cfg['type'] == 'circle':
                array.append(StrangeFloat.encode(float(element_cfg['center'][0]), 'big'))
                array.append(StrangeFloat.encode(float(element_cfg['center'][1]), 'big'))
                array.append(StrangeFloat.encode(float(element_cfg['radius']), 'big'))
                array.append(StrangeInteger.encode(int(element_cfg['width']), 'big'))
                array.append(bytes([element_cfg['color'][0], element_cfg['color'][1], element_cfg['color'][2]]))
            elif element_cfg['type'] == 'ellipse':
                array.append(StrangeFloat.encode(float(element_cfg['position'][0]), 'big'))
                array.append(StrangeFloat.encode(float(element_cfg['position'][1]), 'big'))
                array.append(StrangeFloat.encode(float(element_cfg['size'][0]), 'big'))
                array.append(StrangeFloat.encode(float(element_cfg['size'][1]), 'big'))
                array.append(StrangeInteger.encode(int(element_cfg['width']), 'big'))
                array.append(bytes([element_cfg['color'][0], element_cfg['color'][1], element_cfg['color'][2]]))
            elif element_cfg['type'] == 'line':
                array.append(StrangeFloat.encode(float(element_cfg['start'][0]), 'big'))
                array.append(StrangeFloat.encode(float(element_cfg['start'][1]), 'big'))
                array.append(StrangeFloat.encode(float(element_cfg['end'][0]), 'big'))
                array.append(StrangeFloat.encode(float(element_cfg['end'][1]), 'big'))
                array.append(StrangeInteger.encode(int(element_cfg['width']), 'big'))
                array.append(bytes([element_cfg['color'][0], element_cfg['color'][1], element_cfg['color'][2]]))
            elif element_cfg['type'] == 'arc':
                array.append(StrangeFloat.encode(float(element_cfg['position'][0]), 'big'))
                array.append(StrangeFloat.encode(float(element_cfg['position'][1]), 'big'))
                array.append(StrangeFloat.encode(float(element_cfg['size'][0]), 'big'))
                array.append(StrangeFloat.encode(float(element_cfg['size'][1]), 'big'))
                array.append(StrangeFloat.encode(float(element_cfg['start']), 'big'))
                array.append(StrangeFloat.encode(float(element_cfg['end']), 'big'))
                array.append(StrangeInteger.encode(int(element_cfg['width']), 'big'))
                array.append(bytes([element_cfg['color'][0], element_cfg['color'][1], element_cfg['color'][2]]))
            elif element_cfg['type'] == 'polygon':
                points = []
                point_list = []
                for point in element_cfg['points']:
                    point_list.clear()
                    point_list.append(StrangeFloat.encode(float(point[0]), 'big'))
                    point_list.append(StrangeFloat.encode(float(point[1]), 'big'))
                    points.append(StrangeArray.encode(point_list))
                array.append(StrangeArray.encode(points))
                array.append(StrangeInteger.encode(int(element_cfg['width']), 'big'))
                array.append(bytes([element_cfg['color'][0], element_cfg['color'][1], element_cfg['color'][2]]))
            element_bytes[element_name] += array.dump_bytes()
            array.array.clear()
        #region encoded_elements
        encoded_elements = StrangeDictionary.encode(element_bytes)
        number = len(encoded_elements)
        if number == 0:
            length = 1
        else:
            length = math.ceil(number / 255)
        if length > 255:
            raise ValueError()
        out += bytes([length])
        out += number.to_bytes(length, 'big')
        out += encoded_elements
        del number, length
        #endregion
        #endregion
        return out

    @staticmethod
    def decode(file:bytes) -> dict:
        file = bytearray(file)
        out = {}
        text = StrangeText()
        #region width
        width_length = file[0]
        del file[0]
        width_bytes = bytearray()
        for _ in range(width_length):
            width_bytes.append(file[0])
            del file[0]
        width = int.from_bytes(width_bytes, 'big')
        out['width'] = width
        del width_bytes, width, width_length
        #endregion
        #region height
        height_length = file[0]
        del file[0]
        height_bytes = bytearray()
        for _ in range(height_length):
            height_bytes.append(file[0])
            del file[0]
        height = int.from_bytes(height_bytes, 'big')
        out['height'] = height
        del height_bytes, height, height_length
        #endregion
        #region surfaces
        file_path_length_length = file[0]
        del file[0]
        file_path_length_bytes = bytearray()
        for _ in range(file_path_length_length):
            file_path_length_bytes.append(file[0])
            del file[0]
        file_path_length = int.from_bytes(file_path_length_bytes, 'big')
        del file_path_length_bytes, file_path_length_length
        surfaces = StrangeDictionary.decode(file)
        for _ in range(file_path_length):
            del file[0]
        out['surfaces'] = {}
        for surface_name in surfaces:
            surface_cfg = {}
            encoded_surface_cfg = StrangeArray.decode(surfaces[surface_name])
            surface_cfg['position'] = (
                StrangeFloat.decode(encoded_surface_cfg[0])[0],
                StrangeFloat.decode(encoded_surface_cfg[1])[0]
            )
            surface_cfg['size'] = (
                StrangeInteger.decode(encoded_surface_cfg[2])[0],
                StrangeInteger.decode(encoded_surface_cfg[3])[0]
            )
            surface_cfg['rotation'] = StrangeFloat.decode(encoded_surface_cfg[4])[0]
            out['surfaces'][surface_name] = surface_cfg
        #endregion
        #region elements
        out['elements'] = {}
        encoded_elements_length_length = file[0]
        del file[0]
        encoded_elements_length_bytes = bytearray()
        for _ in range(encoded_elements_length_length):
            encoded_elements_length_bytes.append(file[0])
            del file[0]
        encoded_elements_length = int.from_bytes(encoded_elements_length_bytes, 'big')
        del encoded_elements_length_bytes, encoded_elements_length_length
        elements = StrangeDictionary.decode(file)
        for _ in range(encoded_elements_length):
            del file[0]
        for element_name in elements:
            element_cfg = {}
            encoded_element_cfg = StrangeArray.decode(elements[element_name])
            text.load_bytes(encoded_element_cfg[0])
            element_cfg['surface'] = text.dump()
            text.load_bytes(encoded_element_cfg[1])
            element_cfg['type'] = text.dump()
            element_type = text.dump()
            if element_type == 'rectangle':
                element_cfg['position'] = (
                    StrangeFloat.decode(encoded_element_cfg[2])[0],
                    StrangeFloat.decode(encoded_element_cfg[3])[0]
                )
                element_cfg['size'] = (
                    StrangeInteger.decode(encoded_element_cfg[4])[0],
                    StrangeInteger.decode(encoded_element_cfg[5])[0]
                )
                element_cfg['width'] = StrangeInteger.decode(encoded_element_cfg[6])[0]
                element_cfg['color'] = (encoded_element_cfg[7][0], encoded_element_cfg[7][1], encoded_element_cfg[7][2])
            elif element_type == 'circle':
                element_cfg['center'] = (
                    StrangeFloat.decode(encoded_element_cfg[2])[0],
                    StrangeFloat.decode(encoded_element_cfg[3])[0]
                )
                element_cfg['radius'] = StrangeFloat.decode(encoded_element_cfg[4])[0]
                element_cfg['width'] = StrangeInteger.decode(encoded_element_cfg[5])[0]
                element_cfg['color'] = (encoded_element_cfg[6][0], encoded_element_cfg[6][1], encoded_element_cfg[6][2])
            elif element_type == 'ellipse':
                element_cfg['position'] = (
                    StrangeFloat.decode(encoded_element_cfg[2])[0],
                    StrangeFloat.decode(encoded_element_cfg[3])[0]
                )
                element_cfg['size'] = (
                    StrangeInteger.decode(encoded_element_cfg[4])[0],
                    StrangeInteger.decode(encoded_element_cfg[5])[0]
                )
                element_cfg['width'] = StrangeInteger.decode(encoded_element_cfg[6])[0]
                element_cfg['color'] = (encoded_element_cfg[7][0], encoded_element_cfg[7][1], encoded_element_cfg[7][2])
            elif element_type == 'line':
                element_cfg['start'] = (
                    StrangeFloat.decode(encoded_element_cfg[2])[0],
                    StrangeFloat.decode(encoded_element_cfg[3])[0]
                )
                element_cfg['end'] = (
                    StrangeFloat.decode(encoded_element_cfg[4])[0],
                    StrangeFloat.decode(encoded_element_cfg[5])[0]
                )
                element_cfg['width'] = StrangeInteger.decode(encoded_element_cfg[6])[0]
                element_cfg['color'] = (encoded_element_cfg[7][0], encoded_element_cfg[7][1], encoded_element_cfg[7][2])
            elif element_type == 'arc':
                element_cfg['position'] = (
                    StrangeFloat.decode(encoded_element_cfg[2])[0],
                    StrangeFloat.decode(encoded_element_cfg[3])[0]
                )
                element_cfg['size'] = (
                    StrangeFloat.decode(encoded_element_cfg[4])[0],
                    StrangeFloat.decode(encoded_element_cfg[5])[0]
                )
                element_cfg['start'] = StrangeFloat.decode(encoded_element_cfg[6])[0]
                element_cfg['end'] = StrangeFloat.decode(encoded_element_cfg[7])[0]
                element_cfg['width'] = StrangeInteger.decode(encoded_element_cfg[8])[0]
                element_cfg['color'] = (encoded_element_cfg[9][0], encoded_element_cfg[9][1], encoded_element_cfg[9][2])
            elif element_type == 'polygon':
                encoded_points = StrangeArray.decode(encoded_element_cfg[2])
                element_cfg['points'] = []
                for encoded_point in encoded_points:
                    encoded_point_array = StrangeArray.decode(encoded_point)
                    element_cfg['points'].append((
                        StrangeFloat.decode(encoded_point_array[0])[0],
                        StrangeFloat.decode(encoded_point_array[1])[0]
                    ))
                element_cfg['width'] = StrangeInteger.decode(encoded_element_cfg[3])[0]
                element_cfg['color'] = (encoded_element_cfg[4][0], encoded_element_cfg[4][1], encoded_element_cfg[4][2])
            out['elements'][element_name] = element_cfg
        #endregion
        return out

    def add_surface(self, name, position, size, rotation=0.0):
        self.surfaces[name] = {
            "position": position,
            "size": size,
            "rotation": rotation
        }

    def add_rectangle_element(self, surface:str, name, position, size, color, width=0):
        self.elements[name] = {
            "type": "rectangle",
            "position": position,
            "surface": surface,
            "size": size,
            "color": color,
            "width": width
        }

    def add_circle_element(self, surface:str, name, center, radius, color, width=0):
        self.elements[name] = {
            "type": "circle",
            "center": center,
            "surface": surface,
            "radius": radius,
            "color": color,
            "width": width
        }

    def add_ellipse_element(self, surface:str, name, position, size, color, width=0):
        self.elements[name] = {
            "type": "ellipse",
            "position": position,
            "surface": surface,
            "size": size,
            "color": color,
            "width": width
        }

    def add_line_element(self, surface:str, name, start, end, color, width=1):
        self.elements[name] = {
            "type": "line",
            "start": start,
            "surface": surface,
            "end": end,
            "color": color,
            "width": width
        }

    def add_arc_element(self, surface:str, name, position, size, start:float, end:float, color, width=1):
        self.elements[name] = {
            "type": "arc",
            "start": start,
            "surface": surface,
            "end": end,
            "color": color,
            "position": position,
            "size": size,
            "width": width
        }

    def add_polygon_element(self, surface:str, name, points, color, width=0):
        self.elements[name] = {
            "type": "polygon",
            "points": points,
            "surface": surface,
            "color": color,
            "width": width
        }

    def render_surface(self):
        image = pygame.Surface((self.width, self.height), pygame.SRCALPHA)
        surfaces = {}
        for surface_name in self.surfaces:
            surface = self.surfaces[surface_name]
            surfaces[surface_name] = pygame.Surface(surface['size'], pygame.SRCALPHA)
        for element_name in self.elements:
            element = self.elements[element_name]
            surface = surfaces[element['surface']]
            if element['type'] == 'rectangle':
                pygame.draw.rect(surface, element['color'], pygame.Rect(*element['position'], *element['size']), element['width'])
            if element['type'] == 'circle':
                pygame.draw.circle(surface, element['color'], element['center'], element['radius'], element['width'])
            if element['type'] == 'ellipse':
                pygame.draw.ellipse(surface, element['color'], pygame.Rect(*element['position'], *element['size']), element['width'])
            if element['type'] == 'line':
                pygame.draw.line(surface, element['color'], element['start'], element['end'], element['width'])
            if element['type'] == 'arc':
                pygame.draw.arc(surface, element['color'], pygame.Rect(*element['position'], *element['size']), element['start'], element['end'], element['width'])
            if element['type'] == 'polygon':
                pygame.draw.polygon(surface, element['color'], element['points'], element['width'])
        for surface_name in surfaces:
            surface = surfaces[surface_name]
            surface_cfg = self.surfaces[surface_name]
            surface = pygame.transform.rotate(surface, surface_cfg['rotation'])
            image.blit(surface, surface_cfg['position'])
        return image
